﻿#region Copyright & licence

// This file is part of Refix.
// 
// Refix is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
// 
// Refix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with Refix.  If not, see <http://www.gnu.org/licenses/>.
// 
// Copyright (C) 2010-11 David Musgrove and others.

#endregion

using System;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

using Refix.Core.Repository;

namespace Refix.Core.IO
{
    [Serializable]
    public class ConfigurationFile
    {
        private const string BINDING_SCHEMA = "urn:schemas-microsoft-com:asm.v1";

        private const string EMPTY_CONFIG_FILE =
            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<configuration>\r\n</configuration>";

        static private readonly XmlNamespaceManager _namespaceManager;
        private XDocument _configurationFile;
        private string _fileName;

        static ConfigurationFile()
        {
            _namespaceManager = GetNamespaceManager();
        }

        private ConfigurationFile() {}

        public ConfigurationFile(string fileName)
        {
            _fileName = fileName;
            _configurationFile = XDocument.Load(fileName);
        }

        static public ConfigurationFile CreateNew(string fileName)
        {
            var configurationFile = new ConfigurationFile
                                        {
                                            _fileName = fileName,
                                            _configurationFile = XDocument.Parse(EMPTY_CONFIG_FILE)
                                        };
            return configurationFile;
        }

        public void Save()
        {
            _configurationFile.Save(_fileName);
        }

        public void SafeAddBindingRedirect(Reference assemblyBindingRedirect)
        {
            XElement runtime = EnsureElementExists(_configurationFile.Root, "runtime");
            XElement assemblyBinding = EnsureElementExists(runtime, "assemblyBinding");
            string assemblyXPath =
                string.Format("b:dependentAssembly[b:assemblyIdentity[@name='{0}' and @publicKeyToken='{1}']]",
                              assemblyBindingRedirect.AssemblyName, assemblyBindingRedirect.PublicKeyToken);
            XElement dependentAssembly =
                assemblyBinding.XPathSelectElement(assemblyXPath, _namespaceManager) ??
                AddDependentAssemblyElement(assemblyBindingRedirect, assemblyBinding);
            XElement bindingRedirect = EnsureElementExists(dependentAssembly, "bindingRedirect");
            bindingRedirect.SetAttributeValue(XName.Get("oldVersion"), assemblyBindingRedirect.SpecifiedVersion);
            bindingRedirect.SetAttributeValue(XName.Get("newVersion"), assemblyBindingRedirect.ResolvedVersion);
        }

        static private XElement AddDependentAssemblyElement(Reference assemblyBindingRedirect, XElement assemblyBinding)
        {
            var dependentAssembly = new XElement(XName.Get("dependentAssembly", BINDING_SCHEMA));
            assemblyBinding.Add(dependentAssembly);
            var assemblyIdentity = new XElement(XName.Get("assemblyIdentity", BINDING_SCHEMA));
            dependentAssembly.Add(assemblyIdentity);
            assemblyIdentity.SetAttributeValue(XName.Get("name"), assemblyBindingRedirect.AssemblyName);
            assemblyIdentity.SetAttributeValue(XName.Get("publicKeyToken"), assemblyBindingRedirect.PublicKeyToken);
            assemblyIdentity.SetAttributeValue(XName.Get("culture"),
                                               string.IsNullOrEmpty(assemblyBindingRedirect.Culture)
                                                   ? "neutral"
                                                   : assemblyBindingRedirect.Culture);
            return dependentAssembly;
        }

        static private XmlNamespaceManager GetNamespaceManager()
        {
            var namespaceManager = new XmlNamespaceManager(new NameTable());
            namespaceManager.AddNamespace("b", BINDING_SCHEMA);
            return namespaceManager;
        }

        static private XElement EnsureElementExists(XElement parent, string elementName)
        {
            string prefix = elementName == "runtime" ? "" : "b:";
            XElement element = parent.XPathSelectElement(prefix + elementName, _namespaceManager);
            if (element == null)
            {
                if (prefix == "")
                {
                    element = new XElement(XName.Get(elementName));
                }
                else
                {
                    element = new XElement(XName.Get(elementName, BINDING_SCHEMA));
                }
                parent.Add(element);
            }
            return element;
        }
    }
}